//*******************************************************************************
// Source http://blogs.msdn.com/llobo/archive/2006/12/08/drag-drop-library.aspx *
// Some changes by Stefan Dobrev                                                *
//*******************************************************************************
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using Bookvar.UI.Adorners;

namespace Bookvar.UI.DragDrop
{
    public static class DragDropManager
    {
        private static UIElement _draggedElt;
        private static Point _dragStartPoint;
        private static bool _isMouseDown ;
        private static Point _offsetPoint;
        private static DropPreviewAdorner _overlayElt;

        #region Dependency Properties

        public static readonly DependencyProperty DragSourceAdvisorProperty =
            DependencyProperty.RegisterAttached( "DragSourceAdvisor", typeof( IDragSourceAdvisor ),
                                                 typeof( DragDropManager ),
                                                 new FrameworkPropertyMetadata(
                                                     new PropertyChangedCallback( OnDragSourceAdvisorChanged ) ) );

        public static readonly DependencyProperty DropTargetAdvisorProperty =
            DependencyProperty.RegisterAttached( "DropTargetAdvisor", typeof( IDropTargetAdvisor ),
                                                 typeof( DragDropManager ),
                                                 new FrameworkPropertyMetadata(
                                                     new PropertyChangedCallback( OnDropTargetAdvisorChanged ) ) );

        public static void SetDragSourceAdvisor( DependencyObject dependencyObject, IDragSourceAdvisor advisor )
        {
            dependencyObject.SetValue( DragSourceAdvisorProperty, advisor );
        }

        public static void SetDropTargetAdvisor( DependencyObject dependencyObject, IDropTargetAdvisor advisor )
        {
            dependencyObject.SetValue( DropTargetAdvisorProperty, advisor );
        }

        public static IDragSourceAdvisor GetDragSourceAdvisor( DependencyObject dependencyObject )
        {
            return dependencyObject.GetValue( DragSourceAdvisorProperty ) as IDragSourceAdvisor;
        }

        public static IDropTargetAdvisor GetDropTargetAdvisor( DependencyObject dependencyObject )
        {
            return dependencyObject.GetValue( DropTargetAdvisorProperty ) as IDropTargetAdvisor;
        }

        #endregion

        #region Property Change handlers

        private static void OnDragSourceAdvisorChanged( DependencyObject depObj, DependencyPropertyChangedEventArgs args )
        {
            UIElement sourceElt = depObj as UIElement;
            if ( args.NewValue != null && args.OldValue == null )
            {
                sourceElt.PreviewMouseLeftButtonDown += DragSource_PreviewMouseLeftButtonDown;
                sourceElt.PreviewMouseMove += DragSource_PreviewMouseMove;
                sourceElt.PreviewMouseUp += DragSource_PreviewMouseUp;

                // Set the Drag source UI
                IDragSourceAdvisor advisor = args.NewValue as IDragSourceAdvisor;
                advisor.SourceElement = sourceElt;
            }
            else if ( args.NewValue == null && args.OldValue != null )
            {
                sourceElt.PreviewMouseLeftButtonDown -= DragSource_PreviewMouseLeftButtonDown;
                sourceElt.PreviewMouseMove -= DragSource_PreviewMouseMove;
                sourceElt.PreviewMouseUp -= DragSource_PreviewMouseUp;
            }
        }

        private static void OnDropTargetAdvisorChanged( DependencyObject depObj, DependencyPropertyChangedEventArgs args )
        {
            UIElement targetElt = depObj as UIElement;
            if ( args.NewValue != null && args.OldValue == null )
            {
                targetElt.PreviewDragEnter += DropTarget_PreviewDragEnter;
                targetElt.PreviewDragOver += DropTarget_PreviewDragOver;
                targetElt.PreviewDragLeave += DropTarget_PreviewDragLeave;
                targetElt.PreviewDrop += DropTarget_PreviewDrop;
                targetElt.AllowDrop = true;

                // Set the Drag source UI
                IDropTargetAdvisor advisor = args.NewValue as IDropTargetAdvisor;
                advisor.TargetElement = targetElt;
            }
            else if ( args.NewValue == null && args.OldValue != null )
            {
                targetElt.PreviewDragEnter -= DropTarget_PreviewDragEnter;
                targetElt.PreviewDragOver -= DropTarget_PreviewDragOver;
                targetElt.PreviewDragLeave -= DropTarget_PreviewDragLeave;
                targetElt.PreviewDrop -= DropTarget_PreviewDrop;
                targetElt.AllowDrop = false;
            }
        }

        private static void targetElt_PreviewMouseLeftButtonUp( object sender, MouseButtonEventArgs e )
        {
        }

        #endregion

        /* ____________________________________________________________________
		 *		Drop Target events 
		 * ____________________________________________________________________
		 */

        private static void DropTarget_PreviewDrop( object sender, DragEventArgs e )
        {
            if ( UpdateEffects( sender, e ) == false )
            {
                return;
            }

            IDropTargetAdvisor advisor = GetDropTargetAdvisor( sender as DependencyObject );
            Point dropPoint = e.GetPosition( sender as UIElement );

            // Calculate displacement for (Left, Top)
            //Point offset = e.GetPosition( _overlayElt );
            dropPoint.X = dropPoint.X - _offsetPoint.X;
            dropPoint.Y = dropPoint.Y - _offsetPoint.Y;

            advisor.OnDropCompleted( e, dropPoint );
            RemovePreviewAdorner();
            _offsetPoint = new Point( 0, 0 );
        }

        private static void DropTarget_PreviewDragLeave( object sender, DragEventArgs e )
        {
            if ( UpdateEffects( sender, e ) == false )
            {
                return;
            }

            RemovePreviewAdorner();
//            e.Handled = true;
        }

        private static void DropTarget_PreviewDragOver( object sender, DragEventArgs e )
        {
            if ( UpdateEffects( sender, e ) == false )
            {
                return;
            }
            // Update position of the preview Adorner
            Point position = e.GetPosition( sender as UIElement );
            _overlayElt.Left = position.X - _offsetPoint.X;
            _overlayElt.Top = position.Y - _offsetPoint.Y;

            e.Handled = true;
        }

        private static void DropTarget_PreviewDragEnter( object sender, DragEventArgs e )
        {
            if ( UpdateEffects( sender, e ) == false )
            {
                return;
            }

            // Setup the preview Adorner
            DependencyObject senderDependencyObject = sender as DependencyObject;
            UIElement feedbackUI = GetDropTargetAdvisor( senderDependencyObject ).GetVisualFeedback( e.Data );
            _offsetPoint = GetDropTargetAdvisor( senderDependencyObject ).GetOffsetPoint( e.Data );
            CreatePreviewAdorner( sender as UIElement, feedbackUI );

//            e.Handled = true;
        }

        private static bool UpdateEffects( object uiObject, DragEventArgs e )
        {
            IDropTargetAdvisor advisor = GetDropTargetAdvisor( uiObject as DependencyObject );
            if ( advisor.IsValidDataObject( e.Data ) == false )
            {
                return false;
            }

            if ( ( e.AllowedEffects & DragDropEffects.Move ) == 0 &&
                 ( e.AllowedEffects & DragDropEffects.Copy ) == 0 )
            {
                e.Effects = DragDropEffects.None;
                return true;
            }

            if ( ( e.AllowedEffects & DragDropEffects.Move ) != 0 &&
                 ( e.AllowedEffects & DragDropEffects.Copy ) != 0 )
            {
                if ( ( e.KeyStates & DragDropKeyStates.ControlKey ) != 0 )
                {
                }
                e.Effects = ( ( e.KeyStates & DragDropKeyStates.ControlKey ) != 0 )
                                ?
                                    DragDropEffects.Copy
                                : DragDropEffects.Move;
            }

            return true;
        }

        /* ____________________________________________________________________
		 *		Drag Source events 
		 * ____________________________________________________________________
		 */

        private static void DragSource_PreviewMouseLeftButtonDown( object sender, MouseButtonEventArgs e )
        {
            // Make this the new drag source
            IDragSourceAdvisor advisor = GetDragSourceAdvisor( sender as DependencyObject );
            UIElement clicked = ( advisor.UseOriginalSource ? e.OriginalSource : e.Source ) as UIElement;
            if (advisor.IsDraggable(clicked) == false || e.Handled)
            {
                return;
            }

            _draggedElt = clicked;
            _dragStartPoint = e.GetPosition( GetTopContainer() );
            _offsetPoint = e.GetPosition( _draggedElt );
            _isMouseDown = true;
        }

        private static void DragSource_PreviewMouseMove( object sender, MouseEventArgs e )
        {
            if ( _isMouseDown && IsDragGesture( e.GetPosition( GetTopContainer() ) ) )
            {
                // HACK: THIS IS HACK not to start dragging when resizing an image.
                if ( e.OriginalSource is System.Windows.Controls.Primitives.Thumb )
                {
                    return;
                }

                // HACK: not to drag when selecting text in a text box.
                if ( e.OriginalSource is TextBox )
                {
//                    var textBoxView = (FrameworkElement) e.OriginalSource;
//                    if ( textBoxView.IsFocused )
//                    {
                        return;
//                    }
                }
//                if(e.OriginalSource is MindMapPanel)
//                {
//                    return;
//                }

                DragStarted( sender as UIElement );
            }
        }

        private static void DragSource_PreviewMouseUp( object sender, MouseButtonEventArgs e )
        {
            if ( !e.Handled )
            {
                _isMouseDown = false;
                //Mouse.Capture( null );
            }
        }

        private static void DragStarted( UIElement element )
        {
            _isMouseDown = false;
            element.CaptureMouse();

            IDragSourceAdvisor advisor = GetDragSourceAdvisor( element );
            DataObject data = advisor.GetDataObject( _draggedElt, _offsetPoint );
            DragDropEffects supportedEffects = advisor.SupportedEffects;

            // Perform DragDrop

            DragDropEffects effects = System.Windows.DragDrop.DoDragDrop( _draggedElt, data, supportedEffects );
            advisor.FinishDrag( _draggedElt, effects );

            // Clean up
            RemovePreviewAdorner();
            element.ReleaseMouseCapture();
            _draggedElt = null;
        }

        private static bool IsDragGesture( Point point )
        {
            bool hGesture = Math.Abs( point.X - _dragStartPoint.X ) > 20;//SystemParameters.MinimumHorizontalDragDistance;
            bool vGesture = Math.Abs( point.Y - _dragStartPoint.Y ) > 20;//SystemParameters.MinimumVerticalDragDistance;

            return ( hGesture | vGesture );
        }

        private static UIElement GetTopContainer()
        {
            return Application.Current.MainWindow.Content as UIElement;
        }

        private static void CreatePreviewAdorner( UIElement adornedElt, UIElement feedbackUI )
        {
            // Clear if there is an existing preview adorner
            RemovePreviewAdorner();

            AdornerLayer layer = AdornerLayer.GetAdornerLayer( GetTopContainer() );
            _overlayElt = new DropPreviewAdorner( feedbackUI, adornedElt );
            layer.Add( _overlayElt );
        }

        private static void RemovePreviewAdorner()
        {
            if ( _overlayElt != null )
            {
                AdornerLayer.GetAdornerLayer( GetTopContainer() ).Remove( _overlayElt );
                _overlayElt = null;
            }
        }
    }
}